/**
**************************************************************************************
*Founder Effect                                                                      *
*Copyright (C) 2006-2007, 5d Studios (www.5d-Studios.com)                            *
*                                                                                    *
*This library is free software; you can redistribute it and/or                       *
*modify it under the terms of the GNU Lesser General Public                          *
*License as published by the Free Software Foundation; either                        *
*version 2.1 of the License, or (at your option) any later version.                  *
*                                                                                    *
*This library is distributed in the hope that it will be useful,                     *
*but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                   *
*Lesser General Public License for more details.                                     *
*                                                                                    *
*You should have received a copy of the GNU Lesser General Public                    *
*License along with this library; if not, write to the Free Software                 *
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA      *
**************************************************************************************
*/
#include "stdafx.h"
#include <math.h>
#include "IEntity.h"
#include "IWeapon.h"
#include "PhysicsObject.h"
#include "Tracer.h"

#include "../render/ViewManager.h"
#include "../render/EmitterFactory.h"
#include "../render/BloodEmitter.h"
#include "../render/BleedingEmitter.h"
#include "../render/SwarmEmitter.h"
#include "../render/SmokeEmitter.h"
#include "../render/CartridgeEmitter.h"

#include "../Kernel.h"

extern Kernel* g_kernel;

/* ----------------------Defines------------------------------------*/

#define TIME g_kernel->GetTime()
#define RUN_SCRIPT( str ) (g_kernel->GetScriptEngine()->CallFunction( str ))

/* -----------------------------------------------------------------*/

/**
    Create a Blood Effect --- TEMP!!!
*/
void CreateBlood(const Vector2f &pos)
{
    BloodEmitter* blood = new BloodEmitter;
    blood->SpawnRate             = 30;                    // instant spawn
    blood->MaxParticles          = 8;                    // max particles at a time
    blood->MaxTotalParticles     = 7;                    // max total
    blood->LifeSpan              = 1500;                  // life span of a particles
    blood->LifeSpanOfEmitter     = TIME + 2000;          // life span of the emitter
    blood->StartColor            = Vector3f( 0.596f, 0.0313f, 0.0313f );
    blood->EndColor              = Vector3f( 0.596f, 0.0313f, 0.0313f );
    blood->ColorFadeTime         = 0.0167f;              // color fade depreciation
    blood->StartAlpha            = 1.0f;                 // starting alpha value of particles
    blood->AlphaFadeTime         = 10;                   // time to fade alpha
    blood->ParticleSize          = 7.0f;                 // diameter of particle
    blood->Acceleration          = Vector2f( 44.05f, 44.05f );   
    blood->Position              = pos + Vector2f( 32, 32);  // starting position

    // send the new particle to the particle manager
    IEvent evt(EVT_ADD_PARTICLE, TIME, new ParticleData(blood), LT_PARTICLEMNG );
    EventManager::GetInstance()->TriggerEvent( evt );

	SwarmEmitter* swarm = new SwarmEmitter;
	swarm->SpawnRate			= 100;
	swarm->MaxParticles			= 15;
	swarm->MaxTotalParticles	= 14;
	swarm->LifeSpan				= 4000;
	swarm->LifeSpanOfEmitter	= TIME + 4000;
	swarm->StartColor			= Vector3f( 0.0f, 0.0f, 0.0f);
	swarm->EndColor				= Vector3f( 0.0f, 0.0f, 0.0f);
	swarm->ColorFadeTime		= 0.0157f;
	swarm->StartAlpha			= 1.0f;
	swarm->AlphaFadeTime		= 2000;
	swarm->ParticleSize			= 1.78f;
	swarm->Position				= pos + Vector2f(32,32);

    // send the new particle to the particle manager
    IEvent evt1(EVT_ADD_PARTICLE, TIME, new ParticleData(swarm), LT_PARTICLEMNG );
    EventManager::GetInstance()->TriggerEvent( evt1 );	

	
}

/*------------------------------------------------------------------------
    BaseEntity
--------------------------------------------------------------------------*/

IEntity::IEntity()
{
    m_active    = true; 
    m_remove    = false;
    m_moveable  = false;
    m_readInput = true;
    m_name      = "Entity"; 
    
    m_bounds.Reset();
    m_actions       = 0;
    m_health        = 0;
    m_flags         = 0;
    m_id            = 0; 
    m_nextUpdate    = 0.0f;
    m_moveTime      = 0.0f;
    m_face          = SOUTH; 
    m_oldFace       = SOUTH;
    m_fSpeed        = 0.0f;
    m_fPushSpeed    = 0.0f;
    m_fThrowSpeed   = 0.0f;
    m_heightAboveGround = 0.0f;
    m_fGravity       = 0.0f;
    m_fPushWaitTime = 0.0f;
    m_weaponChangeTime = 0.0f;

    m_vDestination.ZeroOut();   
    m_vThrowVelocity.ZeroOut();
    m_vForward.ZeroOut();
    m_vDirection.ZeroOut();
    m_physObject.SetMass( 1.0f );

    m_carriedEntity     = NULL;
    m_carrier           = NULL;

    m_entCtrl.SetEntity( this );
    m_FSM.SetOwner( this );
}

IEntity::IEntity(const IEntity &ent)
{
    m_active        = ent.m_active; 
    m_remove        = ent.m_remove;;
    m_name          = ent.m_name; 
    m_moveable      = ent.m_moveable;
    
    m_bounds        = ent.m_bounds;
    m_health        = ent.m_health;
    m_flags         = ent.m_flags;
    m_face          = ent.m_face; 
    m_oldFace       = ent.m_oldFace;
    m_fSpeed        = ent.m_fSpeed;
    m_fPushSpeed    = ent.m_fPushSpeed;
    m_fThrowSpeed   = ent.m_fThrowSpeed;

    m_vDestination   .Set( ent.m_vDestination );   
    m_vThrowVelocity .Set( ent.m_vThrowVelocity );
    m_vForward       .Set ( ent.m_vForward );
    m_vDirection     .Set( ent.m_vDirection );
    
    m_physObject.SetMass( 1.0f );

    m_actions       = 0;
    m_id            = 0;
    m_nextUpdate    = 0.0f;
    m_moveTime      = 0.0f;
    m_heightAboveGround = 0.0f;
    m_fGravity       = 0.0f;
    m_fPushWaitTime = 0.0f;
    m_weaponChangeTime = 0.0f;
    m_carriedEntity = NULL;
    m_carrier       = NULL;

    // TODO - copy the dictionary

    m_entCtrl.SetEntity( this );
    m_FSM.SetOwner( this );
}

// TODO - delete states
IEntity:: ~IEntity()
{
    // Delete all the states
    type_States::iterator it = m_states.begin();
    for( ; it != m_states.end(); it++ )
    {
        if ( it->second )
            delete it->second;
    }
}

/** Apply Common Dictionary Attributes */
void IEntity::Apply()
{
    Vector2f position   = m_attributes.GetVector2f("location");
    m_physObject        .SetPosition( position );
    m_fSpeed            = m_attributes.GetInt("base_speed");        // Get the common speeds
    m_fPushSpeed        = m_attributes.GetInt("push_speed");
    m_fThrowSpeed       = m_attributes.GetInt("throw_speed");
    m_health            = m_attributes.GetInt("health");

    UpdateCollisionBounds( position.x, position.y );
    SetMoveable( m_attributes.Has("pushable") );
}

/** Update bounds */
void IEntity::UpdateCollisionBounds( int x, int y )
{
    int width       = m_attributes.GetInt("width");         // Get the dimensions of the bounding box
    int height      = m_attributes.GetInt("height");

    Vector2f boundingBoxOffset = Vector2f( x, y ) + m_attributes.GetVector2f( "bboffset" );
    SetBounds(boundingBoxOffset.x, boundingBoxOffset.y, width, height );
}

/** Get the Reach rectangle - creates a rect infront of entity */
Rect IEntity::GetReachRectangle()
{
    // distance to move 
    int w = GetBounds().width;
    int h = GetBounds().height;

    Vector2f reachPosition( m_physObject.GetPosition() );   
    switch( GetFace() )
    {
    case EAST:  
        reachPosition.y += h; 
        reachPosition.x += w; 
        break;
    case SOUTH: 
        reachPosition.y += (h*2);
        break;
    case WEST:  
        reachPosition.y += h; 
        reachPosition.x -= w;
        break;
    default: break;
    }
    return Rect(reachPosition.x, reachPosition.y, w, h);
}



/** Set the direction */
void IEntity::SetFace( int f)
{ 
    m_oldFace   = m_face; 
    m_face      = f; 

    m_vForward.ZeroOut();

    // make the correct forward vec
    switch( m_face )
    {
    case NORTH: m_vForward.Set( 0, -1 );
        break;
    case SOUTH: m_vForward.Set( 0, 1 );
        break;
    case EAST:  m_vForward.Set( 1, 0 ); 
        break;
    case WEST:  m_vForward.Set( -1, 0 );
        break;
    default:    m_vForward.Set( 1, 1 );
        break;
    };
}


/** Arrived to location */
bool IEntity::ArrivedToLocation() 
{ 
    return TIME > m_moveTime; 
} 

/** Update the entity */
void IEntity::Update( long dt) 
{ 
    m_FSM.Update();

    if ( !m_weaponInventory.IsEmpty() )
    {
        CheckWeaponChange();
        m_weaponInventory.CurrentItem()->Update( dt );
    }

    if ( !ArrivedToLocation() || CarriedBy() )  // we are in the possession of 
        return;                                 // someone else or moving to a location

    CheckFace(m_actions);   // check the direction of the entity
    if( CheckAttack() )     // Check To see if we should attack
        return;
    
    if ( CheckUse() )       // Check to see if we should use
        return;

    CheckMove();            // Check to see if we are moving
    CheckPush();            // Check to see if we are pushing
}

/** Pick Up an Item */
void IEntity::PickUpItem(IEntity* ent)
{
    ent->m_flags &= ~EF_WP_ONGROUND;    // turn off the on ground flag
    ent->m_flags |= EF_NOTONSCREEN;     // don't draw it either
    EntityManager::GetInstance()->RemoveWithoutDeleting( ent );

    // run a picked up script if needed
    if ( ent->m_attributes.Has( "pickedup" ) )
        RUN_SCRIPT( ent->m_attributes.GetStr( "pickedup" ) );

    /**
     * Decide what type of item we picked up, and
     * store it in the proper container
     */
    switch( ent->GetType() )
    {
    case TYPE_BASE:
        m_itemInventory.Add( ent );
        break;
    case TYPE_WEAPON:
        IWeapon* weapon = static_cast<IWeapon*>(ent);
        weapon->SetOwner( this );

        m_weaponInventory.Add( weapon );
        break;
    }
}

/** Damage this entity */
int IEntity::Damage( int hit )
{
    if ( !m_attributes.Has("health") )  // Make sure this entity is destructable
        return 0;

    if ( CarryingEntity() )             // if we are holding something, throw it   
        Throw();

    int old   = m_health;               // save old health
    m_health += hit;                    // damage the health

    SetState( ENT_STATES[HIT] );        // set to the hit state
    if ( m_health <= 0 )                // set the dead state 
	{
        SetState(ENT_STATES[DEAD]);
		BleedingEmitter* blood = new BleedingEmitter;
		blood->SpawnRate             = 0;                    // instant spawn
		blood->MaxParticles          = 1;                    // max particles at a time
		blood->MaxTotalParticles     = 0;                    // max total
		blood->LifeSpan              = 9000;                  // life span of a particles
		blood->LifeSpanOfEmitter     = TIME + 9000;          // life span of the emitter
		blood->StartColor            = Vector3f( 0.596f, 0.0313f, 0.0313f );
		blood->EndColor              = Vector3f( 0.596f, 0.0313f, 0.0313f );
		blood->ColorFadeTime         = 0.00055f;              // color fade depreciation
		blood->StartAlpha            = 1.0f;                 // starting alpha value of particles
		blood->AlphaFadeTime         = 500;                   // time to fade alpha
		blood->ParticleSize          = .1f;                 // diameter of particle
		blood->Acceleration          = Vector2f( 44.05f, 44.05f );   
		blood->Position              = m_physObject.GetPosition() + Vector2f( 32, 32);  // starting position
	
		// send the new particle to the particle manager
		IEvent evt(EVT_ADD_PARTICLE, TIME, new ParticleData(blood), LT_PARTICLEMNG );
		EventManager::GetInstance()->TriggerEvent( evt );



	}

    if ( hit < 0 )                      // Temp Blood effect
        CreateBlood(m_physObject.GetPosition());

    return old;
}

/** This entity is carrying something */
void IEntity::Carry( IEntity* ent ) 
{
    SetState( ENT_STATES[CARRYING] );       // Set the entities state to carrying
    ent->SetState( ENT_STATES[CARRIED] );   // set the other entity to being carried

    ent->Carrier( this );                   // Make sure the carried ent knows who's carrying it
    m_carriedEntity = ent;      
    
}

/** Throws the entity */
void IEntity::Throw()
{
    if ( CarryingEntity() )
    {
        SetState( ENT_STATES[THROWING] );
        m_carriedEntity->SetState( ENT_STATES[THROWN] );
        
        m_carriedEntity->m_heightAboveGround = m_bounds.height;     // Set the height
        m_carriedEntity->MoveToOverTime( m_vForward, TIME + 500 );

        m_carriedEntity->m_carrier  = NULL; 
        m_carriedEntity             = NULL; 
    }
}

/** Attack */
void IEntity::Attack()
{
    // only attack if we have a weapon!
    if ( !m_weaponInventory.IsEmpty() )
    {
        IWeapon* weapon = m_weaponInventory.CurrentItem();
        if ( weapon )
		{
            weapon->Fire();
			
			SmokeEmitter* smoke = new SmokeEmitter;
			smoke->SpawnRate             = 23;                    // instant spawn
			smoke->MaxParticles          = 18;                    // max particles at a time
			smoke->MaxTotalParticles     = 17;                    // max total
			smoke->LifeSpan              = 1100;                  // life span of a particles
			smoke->LifeSpanOfEmitter     = TIME + 1100;          // life span of the emitter
			smoke->StartColor            = Vector3f( 0.25f, 0.25f, 0.25f );
			smoke->EndColor              = Vector3f( 1.0f, 1.0f, 1.0f );
			smoke->ColorFadeTime         = 0.0167f;              // color fade depreciation
			smoke->StartAlpha            = 0.7f;                 // starting alpha value of particles
			smoke->AlphaFadeTime         = 10;                   // time to fade alpha
			smoke->ParticleSize          = 2.0f;                 // diameter of particle
			smoke->Acceleration          = Vector2f( 44.05f, 44.05f );   
			smoke->Position              = m_physObject.GetPosition() + Vector2f( 32, 32);  // starting position

			// send the new particle to the particle manager
			IEvent evt2(EVT_ADD_PARTICLE, TIME, new ParticleData(smoke), LT_PARTICLEMNG );
			EventManager::GetInstance()->TriggerEvent( evt2 );

			CartridgeEmitter* cartridge = new CartridgeEmitter;
			cartridge->SpawnRate             = 0;                    // instant spawn
			cartridge->MaxParticles          = 1;                    // max particles at a time
			cartridge->MaxTotalParticles     = 0;                    // max total
			cartridge->LifeSpan              = 2300;                  // life span of a particles
			cartridge->LifeSpanOfEmitter     = TIME + 2300;          // life span of the emitter
			cartridge->StartColor            = Vector3f( 1.0f, 1.0f, 0.0f );
			cartridge->EndColor              = Vector3f( 1.0f, 1.0f, 0.0f );
			cartridge->ColorFadeTime         = 1.0f;                 // color fade depreciation
			cartridge->StartAlpha            = 1.0f;                 // starting alpha value of particles
			cartridge->AlphaFadeTime         = 10000;                   // time to fade alpha
			cartridge->ParticleSize          = 0.2f;                 // diameter of particle
			cartridge->Acceleration          = Vector2f( 0.0f, 2.05f );   
			cartridge->Position              = m_physObject.GetPosition() + Vector2f( 32, 32);  // starting position
			cartridge->InitVel			     = Vector2f(0,0);

			// send the new particle to the particle manager
			IEvent evt3(EVT_ADD_PARTICLE, TIME, new ParticleData(cartridge), LT_PARTICLEMNG );
			EventManager::GetInstance()->TriggerEvent( evt3 );

		}
    }
    
}

/** Use */
void IEntity::Use()
{
    Tracer tracer;
    // search for an entity in a region
    IEntity* ent = tracer.FindClosests( GetReachRectangle(), this );
    if ( ent )
    {  
        if ( ent->m_attributes.Has( "dialog" ) ) 
        {
            ent->SetFace( (GetFace() + 2) % 4);
            ent->SetState( ENT_STATES[TALKING] );
        }
        else if ( ent->m_attributes.Has( "liftable" ) ) 
            if ( !CarryingEntity() )
                Carry( ent );
    }
}

/** Push this block */
void IEntity::Push( IEntity* pusher )
{
    if ( GetState() == ENT_STATES[PUSHED] )
        return;

    SetState( ENT_STATES[PUSHED] );

    // move this entity in the direction
    // the pusher is facing
    MoveToOverTime( pusher->m_vForward, TIME + 210 );

    //m_vDirection = pusher->m_vForward;
    //int pushAmount = m_attributes.GetInt( "pushable" );
    //Rect forward = pusher->GetReachRectangle();
    //Vector2f moveFrom = pusher->m_physObject.GetPosition() + m_vDirection*forward.width;
    //m_vDestination = moveFrom + (m_vDirection * pushAmount);
    ////m_physObject.SetFriction( 0.98f );
    ////m_physObject.ApplyForce( (m_vDirection * pushAmount) );
    //m_physObject.SetDestination( m_vDestination );
    
}

/** Check for pushing */
void IEntity::CheckPush()
{
    Vector2f currentPos = m_physObject.GetPosition();
    if ( m_vOldPosition.Equals( currentPos, 0.01f ) && 
         GetState() == ENT_STATES[WALKING] )
    {
        if ( TIME > m_fPushWaitTime ) {
            Tracer tracer;
            // search for an entity in a region
            IEntity* ent = tracer.FindClosests( GetReachRectangle(), this );
            if ( ent && ent->IsMoveable() )
            {
                ent->Push( this );
                SetState( ENT_STATES[PUSHING] );
                return;
            }
        }
    } else m_fPushWaitTime = TIME + 500; // wait a half second
    m_vOldPosition = currentPos;
}

/** Check the face Direction of the player */
void IEntity::CheckFace(int action)
{
    m_vDirection.ZeroOut();
    int lastFace = GetFace(); 

    // vertical movement
    if( action & BUTTON_UP ) {
        m_vDirection.y = -1;
        if( lastFace == SOUTH || !(action & (BUTTON_LEFT|BUTTON_RIGHT)) ) {
            if ( !(action & BUTTON_STRAFE) )
                SetFace(NORTH);
        }
    } else if ( action & BUTTON_DOWN ) {
        m_vDirection.y = 1;
        if( lastFace == NORTH  || !(action & (BUTTON_LEFT|BUTTON_RIGHT)) ) {
            if ( !(action & BUTTON_STRAFE) )
                SetFace(SOUTH);
        }
    }

    // horizontal movement
    if( action & BUTTON_LEFT ) {
        m_vDirection.x = -1;
        if( lastFace == EAST || !(action & (BUTTON_UP|BUTTON_DOWN)) ) {
            if ( !(action & BUTTON_STRAFE) )
                SetFace(WEST);
        } 
    } else if ( action & BUTTON_RIGHT ) {
        m_vDirection.x = 1;
        if( lastFace == WEST || !(action & (BUTTON_UP|BUTTON_DOWN)) ) {
            if ( !(action & BUTTON_STRAFE) )
                SetFace(EAST);
        }
    }

}

/** Check for movement */
void IEntity::CheckMove()
{
    // Check for movement
    if ( m_vDirection.Length() )
    {
        // switch the state to walking
        if ( CarryingEntity() )
            SetState( ENT_STATES[CARRYING_WALKING] );
        else
            SetState( ENT_STATES[WALKING] );
    } 
    else if ( GetState() != ENT_STATES[TALKING] )
    {
        // switch the state to idle
        if ( CarryingEntity() )
            SetState( ENT_STATES[CARRYING] );
        else
            SetState( ENT_STATES[IDLE] ); 
    }
}


/** Check for attacking */
bool IEntity::CheckAttack()
{
    // The entity is attempting to attack
    if ( m_actions & BUTTON_ATTACK1 )
    {
        if ( CarryingEntity() )
            Throw();
        else Attack();          // Unleash Furry dammit

        return true;
    }
    return false;
}

/** Check for use */
bool IEntity::CheckUse()
{
    // The entity is attempting to 'use'
    if ( m_actions & BUTTON_USE )
    {
        if ( CarryingEntity() )
            Throw();
        else 
            Use();
        return true;
    }
    return false;
}


/** Check for weapon change */
void IEntity::CheckWeaponChange()
{
    if ( TIME > m_weaponChangeTime )   
    {
        if ( m_actions & BUTTON_NEXT )
        {
            m_weaponInventory.NextItem();
            m_weaponChangeTime = TIME + 300;
        }
        if ( m_actions & BUTTON_PREV )
        {
            m_weaponInventory.PrevItem();
            m_weaponChangeTime = TIME + 300;    // TEMP
        }
    }
}